﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using Strategy.Core.Generator.XML.Classes;

namespace Strategy.Core.Generator
{
    public class StrategyGenerator
    {
        protected string targetNameSpace = "";
        protected string fullPath = "";
        protected string actionsPath { get { return fullPath + "Actions\\"; } }
        protected string conditionsPath { get { return fullPath + "Conditions\\"; } }
        protected Context context = null;

        protected void generateDirectoriesAndFiles()
        {
            Directory.CreateDirectory(actionsPath);
            Directory.CreateDirectory(conditionsPath);
            
        }
        protected Project getCSPROJ()
        {
            Project p = new Project()
            { 
                ToolsVersion = 4, 
                DefaultTargets = "Build"
            };
            #region groups
            ProjectPropertyGroup ppg1 = new ProjectPropertyGroup();
            ppg1.Configuration = new ProjectPropertyGroupConfiguration()
            {
                Condition = " '$(Configuration)' == '' ",
                Value = "Release"
            };
            ppg1.Platform = new ProjectPropertyGroupPlatform()
            {
                Condition = " '$(Platform)' == '' ",
                Value = "x86"
            };
            ppg1.SchemaVersion = 2;
            ppg1.OutputType = "Exe";
            ppg1.AppDesignerFolder = "Properties";
            ppg1.RootNamespace = targetNameSpace;
            ppg1.AssemblyName = targetNameSpace;
            ppg1.TargetFrameworkVersion = "v4.0";
            ppg1.TargetFrameworkProfile = "Client";
            ppg1.FileAlignment = 512;

            ProjectPropertyGroup ppg2 = new ProjectPropertyGroup()
            {
                Condition = " '$(Configuration)|$(Platform)' == 'Debug|x86' ",
                PlatformTarget = "x86",
                DebugSymbols = true,
                DebugType = "full",
                Optimize = false,
                OutputPath = @"bin\Debug",
                DefineConstants = "DEBUG;TRACE",
                ErrorReport = "prompt",
                WarningLevel = 4,
                OutputType = "Library"
            }; 
            ProjectPropertyGroup ppg3 = new ProjectPropertyGroup()
            {
                Condition = " '$(Configuration)|$(Platform)' == 'Release|x86' ",
                PlatformTarget = "x86",
                DebugType = "pdbonly",
                Optimize = true,
                OutputPath = @"bin\Release",
                DefineConstants = "TRACE",
                ErrorReport = "prompt",
                WarningLevel = 4,
                OutputType = "Library"
            };
            #endregion 
            p.PropertyGroup = new ProjectPropertyGroup[] { ppg1, ppg2, ppg3 };

            #region itemgroups
            ProjectItemGroup ig1 = new ProjectItemGroup();
            List<ProjectItemGroupReference> using_strings = new List<ProjectItemGroupReference>();
            using_strings.Add(new ProjectItemGroupReference()
            {
                Include = @"Strategy.Core, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL",
                SpecificVersion = "false",
                HintPath = @".\Strategy.Core.dll"
                
            });
            foreach (var el in references)
                using_strings.Add(new ProjectItemGroupReference() { Include = el });
            ig1.Reference = using_strings.ToArray();

            ProjectItemGroup ig2 = new ProjectItemGroup();
            List<ProjectItemGroupCompile> compile_list = new List<ProjectItemGroupCompile>();
            compile_list.Add(new ProjectItemGroupCompile() { Include = @"ContextImpl.cs" });
            compile_list.Add(new ProjectItemGroupCompile() { Include = @"ModelImpl.cs" });
            compile_list.Add(new ProjectItemGroupCompile() { Include = @"AbstractTraderClass.cs" });
            compile_list.Add(new ProjectItemGroupCompile() { Include = @"Actions\MainAction.cs" });
            foreach (var el in context.Events)
            {
                compile_list.Add(new ProjectItemGroupCompile() { Include = @"Actions\" + getClassActionName(el) + ".cs" });
                compile_list.Add(new ProjectItemGroupCompile() { Include = @"Conditions\" + getClassConditionName(el) + ".cs" });
            }
            ig2.Compile = compile_list.ToArray();
            #endregion
            p.ItemGroup = new ProjectItemGroup[] { ig1, ig2, new ProjectItemGroup() { Content = new ProjectItemGroupContent() { Include = "Strategy.Core.dll"} } };
            p.Import = new ProjectImport() { Project = @"$(MSBuildToolsPath)\Microsoft.CSharp.targets" };
            return p;

        }

        protected virtual void loadXML(string xmlpath)
        {
            StreamReader str = new StreamReader(xmlpath);
            XmlSerializer xSerializer = new XmlSerializer(typeof(Context));
            context = (Context)xSerializer.Deserialize(str);
            str.Close();
        }
        protected void generateCSPROJ()
        {
            Project p = getCSPROJ();
            StreamWriter sw = new StreamWriter(fullPath + targetNameSpace+".csproj");
            XmlSerializer xs = new XmlSerializer(typeof(Project));
            xs.Serialize(sw, p);
            sw.Close();
        }


        #region filesystem generation
        private string getPrivateAndBaseConstructor(string classname, string[] prmstypes,bool isInclPrivate = true)
        {
            StringBuilder sb = new StringBuilder();
            if (isInclPrivate)
                sb.AppendLine("private " + classname + "(){}");
            string s1 = "";
            string s2 = "";
            for (int i = 0; i < prmstypes.Length; ++i)
            {
                string s =  " p"+i.ToString()+(i == prmstypes.Length - 1 ? "" :","); 
                s2 += s;
                s1 += prmstypes[i] +s;
            }
            sb.AppendLine("public " + classname + "(" + s1 + ") : base(" + s2 + "){}");
            return sb.ToString();
        }
        private void generate(string path, string content)
        {
            using (StreamWriter fs = new StreamWriter(path))
            {
                fs.WriteLine(content);
            }
        }
        private string getClassActionName(ContextEvent ev) { return "Action_" + ev.Name + "_" + ev.StartState + "_" + ev.EndState; }
        private void generateAction(Strategy.Core.Generator.XML.Classes.ContextEvent ev)
        {
            string class_name = getClassActionName(ev);
            string content = getClass(targetNameSpace + ".Actions", class_name, "AbstractTraderClass,IAction", 
                new string[] { getMethod("void", "DoAction", ev.Action), 
                               getPrivateAndBaseConstructor(class_name,new string[] { "ITrader","IStrategy","ModelImpl" })});
            generate(actionsPath + class_name+".cs", content);
        }
        private string getClassConditionName(ContextEvent ev) { return "Condition_" + ev.Name + "_" + ev.StartState + "_" + ev.EndState; }
        private void generateCondition(Strategy.Core.Generator.XML.Classes.ContextEvent ev)
        {
            string class_name = getClassConditionName(ev);
            string content = getClass(targetNameSpace + ".Conditions", class_name, "AbstractTraderClass,ICondition",
                new string[] { getMethod("bool", "isFired", "return (" + ev.Condition + ");"), 
                               getPrivateAndBaseConstructor(class_name,new string[] { "ITrader","IStrategy","ModelImpl" }) });
            generate(conditionsPath + class_name + ".cs", content);
        }
        private void generateModelImpl()
        {
            List<string> methods = new List<string>();
            foreach (var el in context.DataModel)
                methods.Add("public virtual " +el.type+" "+el.name+" { get; set;} ");
            methods.Add("public void Save(){}");

            generate(fullPath + "ModelImpl" + ".cs",getClass(targetNameSpace,"ModelImpl","IDataModel",methods.ToArray()));
        }
        private String generateModelImplOverrideMethods()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var el in context.DataModel)
                sb.AppendLine("public override " + el.type + " " + el.name + " { get { return model."+el.name+";} set{ model."+el.name+"=value;}}");
            return sb.ToString();
        }
        private void generateAbstraction()
        {
            List<string> methods = new List<string>();
            
            generate(fullPath+ "AbstractTraderClass.cs", getClass(targetNameSpace,
                                                         "AbstractTraderClass",
                                                         " ModelImpl,ITrader, IStrategy",
                                                         new string[] {generateInterfaceImplDelegate(typeof(Strategy.Core.Trade.IStrategy), "Strategy"),
                                                                       generateInterfaceImplDelegate(typeof(Strategy.Core.Trade.ITrader), "Trader"),
                                                                       generateModelImplOverrideMethods(),
                                                                       "protected IStrategy Strategy;",
                                                                       "protected ITrader Trader;",
                                                                       "protected ModelImpl model;",
                                                                       "protected AbstractTraderClass() { }",
                                                                       "public AbstractTraderClass(ITrader t, IStrategy s, ModelImpl m){ Strategy = s; Trader = t;model = m; }"}));
        }
        private void generateContext()
        {
            List<string> methods = new List<string>();

            StringBuilder initmethod = new StringBuilder();
            initmethod.Append(indentLine("protected override void initImplementation()"));
            initmethod.Append(getBranch(true));
                initmethod.Append(indentLine("DataModel = new ModelImpl();"));
                initmethod.Append(indentLine("Action = new MainAction(Trader, Strategy, (ModelImpl)DataModel);"));
                foreach (var el in context.Events)
                {
                    initmethod.Append(indentLine("createEvent(\"" + el.Name + "\",\"" + el.StartState + "\",\"" + el.EndState + "\","));
                    initmethod.Append(indentLine("\t new " + getClassConditionName(el) + "(Trader,Strategy,(ModelImpl)DataModel),"));
                    initmethod.Append(indentLine("\t new " + getClassActionName(el) + "(Trader,Strategy,(ModelImpl)DataModel) );"));
                }

            initmethod.Append(getBranch(false));
            string clname= "ContextImpl";
            generate(fullPath + clname+".cs", getClass(targetNameSpace,
                                                         clname,
                                                         "AContext",
                                                         new string[] {getPrivateAndBaseConstructor(clname,new string[] { "IStrategy","ITrader"},false),
                                                                       initmethod.ToString()}
                                                         ));
        }
        #endregion

        #region string generated
        protected string[] using_namespaces = { "System","Strategy.Core","Strategy.Core.Trade" };
        protected string[] references = { "Microsoft.CSharp", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Xml", "System.Xml.Linq" };
        protected string getUsingBlock()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var el in using_namespaces)
            {
                sb.Append("using ");
                sb.Append(el);
                sb.Append(";\n");
            }
            sb.Append("using " + targetNameSpace + ".Actions;\n");
            sb.Append("using " + targetNameSpace + ".Conditions;\n");
            sb.Append("using " + targetNameSpace + ";\n");
            sb.Append("\n\n");

            return sb.ToString();
        }        
        
        private int count_open_branch = 0;
        private string getBranch(bool isOpen)
        {
            string res = "";
            if (isOpen)
            {
                res = indentLine("{");
                count_open_branch++;
            }
            else
            {
                count_open_branch--;
                res = indentLine("}");
            }
            return res;
        }
        private string indentLine(string content)
        {
            string res = "";
            for(int i  = 0 ;  i < count_open_branch; ++i)
                res += "\t";
            return res+content+"\n";
        }
        private string getClass(string name_space,string name, string parent, string[] methods,bool isAbstract = false)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(getUsingBlock());
            sb.Append(indentLine("namespace "+name_space));
            sb.Append(getBranch(true));
               
                string cls = "public class " + name;
                if (isAbstract)
                    cls = "public abstract class " + name;
                if (parent != null)
                    cls += " : " + parent;
                sb.Append(indentLine(cls));
                sb.Append(getBranch(true));
                if (methods != null) 
                    foreach (var el in methods) 
                        foreach(var line in el.Split('\n'))
                            sb.Append(indentLine(line));
                sb.Append(getBranch(false));

            sb.Append(getBranch(false));
            return sb.ToString();

        }
        private string getMethod(string returnval, string name,  string content, bool isOvrd = false)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(indentLine("public " + (isOvrd ? " override ": " ") + returnval + " " + name + "()"));
            sb.Append(getBranch(true));
                sb.Append(indentLine(content));
            sb.Append(getBranch(false));
            return sb.ToString();
        }
        private string generateInterfaceImplDelegate(Type t, string delegate_prop)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var el2 in t.GetMethods())
            {
                sb.Append("public ");
                if (el2.ReturnType.Name == "Void")
                    sb.Append("void");
                else
                    sb.Append(el2.ReturnType.Name);
                sb.Append( " " + el2.Name + " ( ");
                foreach (var m in el2.GetParameters())
                    sb.Append(m.ParameterType.Name + " " + m.Name + (m == el2.GetParameters().Last() ? "  " : " , ").ToString());
                sb.Append(" ) { ");
                if (el2.ReturnType.Name != "Void")
                    sb.Append(" return ");
                sb.Append(" " + delegate_prop + "." + el2.Name + " ( ");
                foreach (var m in el2.GetParameters())
                    sb.Append(m.Name + (m == el2.GetParameters().Last() ? "  " : " , ").ToString());
                sb.AppendLine(") ; }");
                
            }
            return sb.ToString();
        }
       
        #endregion

        protected void assertValidateStrategy() { }
        public void GenerateClassesFromXML(string xmlpath, string targetPath, string _targetNamespace)
        {
            loadXML(xmlpath);
            fullPath = targetPath;
            targetNameSpace = _targetNamespace;
            assertValidateStrategy();
            generateDirectoriesAndFiles();
            generateAbstraction();

            generateModelImpl();


            foreach (var el in context.Events)
            {
                generateAction(el);
                generateCondition(el);
            }
            generateContext();


            string main_action = "MainAction";
            string content = getClass(targetNameSpace + ".Actions", main_action, "AbstractTraderClass,IAction", new string[] { getMethod("void", "DoAction", context.Action), getPrivateAndBaseConstructor(main_action, new string[] { "ITrader", "IStrategy", "ModelImpl" }) });
            generate(actionsPath + main_action + ".cs", content);


            generateCSPROJ();
            if (File.Exists("Strategy.Core.dll") && !File.Exists(fullPath + "Strategy.Core.dll"))
                File.Copy("Strategy.Core.dll", fullPath + "Strategy.Core.dll");

        }

    
    }
}
